﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using ConsoleFx;
using NBuild.Fx.Engine;

namespace NBuild.Runner.CommandLine
{
    [ArgumentUsage(ProgramContext.Verify, MinOccurences = 0, MaxOccurences = int.MaxValue)]
    [ArgumentUsage(ProgramContext.Execute, MinOccurences = 0, MaxOccurences = int.MaxValue)]
    [ArgumentUsage(ProgramContext.Help)]
    public sealed class Program : ConsoleProgram
    {
        private string _context = ProgramContext.Execute;

        private string _language;
        private bool _strict;
        private string[] _targets;
        private readonly PropertyOverrideCollection _propertyOverrides = new PropertyOverrideCollection();
        private readonly StringCollection _references = new StringCollection();

        #region Execution methods

        public int ExecuteExecute()
        {
            int result = InternalExecute(true);
            if (result == 0)
                ConsoleEx.WriteLine("Your build succeeded");
            return result;
        }

        public int ExecuteVerify()
        {
            int result = InternalExecute(false);
            if (result == 0)
                ConsoleEx.WriteLine("Your build file(s) are valid");
            return result;
        }

        private int InternalExecute(bool execute)
        {
            ConsoleEx.WriteLine("Build started at {0}", DateTime.Now);

            var sources = new List<string>(SpecifiedArguments.Count);
            foreach (var specifiedArgument in SpecifiedArguments)
                sources.Add(File.ReadAllText(specifiedArgument));

            using (var nbuildEngine = new NBuildEngine())
            {
                nbuildEngine.Sources.AddRange(sources.ToArray());
                nbuildEngine.TreatWarningsAsErrors = _strict;
                nbuildEngine.PropertyOverrides.AddRange(_propertyOverrides);
                if (_targets != null && _targets.Length > 0)
                    nbuildEngine.StartupTargets.AddRange(_targets);
                if (!string.IsNullOrEmpty(_language))
                    nbuildEngine.Language = _language;
                foreach (var reference in _references)
                    nbuildEngine.References.Add(reference);
                string binDirectory = Path.GetDirectoryName(Path.GetFullPath(Environment.GetCommandLineArgs()[0]));
                string tasksDirectory = Path.Combine(binDirectory, "Tasks");
                if (Directory.Exists(tasksDirectory))
                    nbuildEngine.References.AddRange(Directory.GetFiles(tasksDirectory, "*.dll"));

                NBuildResults results = nbuildEngine.Execute(execute ? ExecutionMode.Run : ExecutionMode.Verify);

                Console.WriteLine();
                switch (results.Status)
                {
                    case NBuildStatus.CompileErrors:
                        ConsoleEx.WriteLine(ConsoleColor.Red, null, "{0} errors", results.Errors.Count);
                        ConsoleEx.WriteLine(ConsoleColor.Yellow, null, "{0} warnings", 0);
                        foreach (var error in results.Errors)
                            ConsoleEx.WriteLine("[{0}, {1}] {2}", error.Line, error.Column, error.ErrorText);
                        return -results.Errors.Count;

                    case NBuildStatus.ProcessingErrors:
                        ConsoleEx.WriteLine(ConsoleColor.Red, null, "There was an error processing your build file:");
                        ConsoleEx.WriteLine(results.ProcessingException.Message);
                        return int.MinValue;

                    default:
                        return 0;
                }
            }
        }

        public int ExecuteHelp()
        {
            DisplayUsage();
            return 0;
        }

        #endregion

        #region ConsoleProgram overrides

        public void HandleError(TargetInvocationException tie)
        {
            ConsoleEx.WriteLine(ConsoleColor.Red, null, tie.InnerException.Message);
        }

        public override void DefaultErrorHandler(Exception exception)
        {
            base.DefaultErrorHandler(exception);
            ConsoleEx.WriteLine(ConsoleColor.Red, null, exception.Message);
        }

        public override string GetContext()
        {
            return _context;
        }

        public override void PreProcess()
        {
            base.PreProcess();
            if (SpecifiedArguments.Count == 0)
                SpecifiedArguments.Add("Build.cs");
        }

        #endregion

        #region Switch handling methods

        [Switch("language", ShortName = "l", MinParameters = 1, MaxParameters = 1)]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void LanguageSwitch(string[] parameters)
        {
            _language = parameters[0];
        }

        [Switch("property", ShortName = "p", MinOccurences = 0, MaxOccurences = int.MaxValue, MinParameters = 1, MaxParameters = 1)]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.NotAllowed)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void PropertySwitch(string[] parameters)
        {
            string[] breakup = parameters[0].Split('=');
            if (breakup.Length != 2)
                throw new Exception("Please specify property values in the form 'name=value'");
            _propertyOverrides.Add(new PropertyOverride(breakup[0], breakup[1]));
        }

        [Switch("reference", ShortName = "r", MinParameters = 1, MaxParameters = 1, MinOccurences = 0, MaxOccurences = int.MaxValue)]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void ReferenceSwitch(string[] parameters)
        {
            _references.Add(parameters[0]);
        }

        [Switch("targets", ShortName = "t", MinParameters = 1, MaxParameters = int.MaxValue)]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void TargetsSwitch(string[] parameters)
        {
            _targets = new string[parameters.Length];
            Array.Copy(parameters, _targets, parameters.Length);
        }

        [Switch("strict")]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.Optional)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void StrictSwitch(string[] parameters)
        {
            _strict = true;
        }

        [Switch("verify", ShortName = "v")]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.Mandatory)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.NotAllowed)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.NotAllowed)]
        public void VerifySwitch(string[] parameters)
        {
            _context = ProgramContext.Verify;
        }

        [Switch("help", ShortName = "?")]
        [SwitchUsage(ProgramContext.Verify, SwitchUsage.NotAllowed)]
        [SwitchUsage(ProgramContext.Execute, SwitchUsage.NotAllowed)]
        [SwitchUsage(ProgramContext.Help, SwitchUsage.Mandatory)]
        public void HelpSwitch(string[] parameters)
        {
            _context = ProgramContext.Help;
        }

        #endregion
    }
}
